import {
  _decorator,
  Component,
  instantiate,
  Node,
  Prefab,
  SpriteFrame,
} from "cc";
import DataManager from "../Global/DataManager";
import { JoyStickManager } from "../UI/JoyStickManager";
import { ResourceManager } from "../Global/ResourceManager";
import { ActorManager } from "../Entity/Actor/ActorManager";
import { EventEnum, PrefabPathEnum, TexturePathEnum } from "../Enum";
import {
  ApiMsgEnum,
  EntityTypeEnum,
  IClientInput,
  IMsgClientSync,
  IMsgServerSync,
  InputTypeEnum,
} from "../Common";
import { BulletManager } from "../Entity/Bullet/BulletManager";
import ObjectPoolManager from "../Global/ObjectPoolManager";
import NetworkManager from "../Global/NetworkManager";
import EventManager from "../Global/EventManager";
import { deepClone } from "../Utils";
const { ccclass, property } = _decorator;

@ccclass("BattleManager")
export class BattleManager extends Component {
  private stage: Node;
  private ui: Node;

  private finishLoad: boolean = false;

  pendingMsg: IMsgClientSync[] = [];

  onLoad(): void {}

  protected onDestroy(): void {
    EventManager.Instance.off(
      EventEnum.ClientSync,
      this.handleClientSync,
      this
    );
  }

  async loadRes() {
    let arrPromise = [];

    for (const type in PrefabPathEnum) {
      const p = ResourceManager.Instance.loadRes(
        PrefabPathEnum[type],
        Prefab
      ).then((pref) => {
        DataManager.Instance.mapPrefab.set(type, pref);
      });

      arrPromise.push(p);
    }

    for (const type in TexturePathEnum) {
      console.log(TexturePathEnum[type]);
      const p = ResourceManager.Instance.loadDir(
        TexturePathEnum[type],
        SpriteFrame
      ).then((frames) => {
        DataManager.Instance.mapTexture.set(type, frames);
      });

      arrPromise.push(p);
    }

    await Promise.all(arrPromise);
  }

  initGame() {
    DataManager.Instance.jm = this.ui.getComponentInChildren(JoyStickManager);
    this.initMap();
    this.finishLoad = true;
    EventManager.Instance.on(EventEnum.ClientSync, this.handleClientSync, this);
    NetworkManager.Instance.listenMsg(
      ApiMsgEnum.MsgServerSync,
      this.handleServerSync,
      this
    );
  }

  clearGame() {
    EventManager.Instance.off(
      EventEnum.ClientSync,
      this.handleClientSync,
      this
    );

    NetworkManager.Instance.unlistenMsg(
      ApiMsgEnum.MsgServerSync,
      this.handleServerSync,
      this
    );

    DataManager.Instance.stage = this.stage = this.node.getChildByName("Stage");
    this.ui = this.node.getChildByName("UI");
    this.stage.destroyAllChildren();
  }

  async start() {
    this.clearGame();
    await Promise.all([this.connetServer(), this.loadRes()]);

    this.initGame();
  }

  handleClientSync(input: IClientInput) {
    let msg: IMsgClientSync = {
      input,
      frameId: DataManager.Instance.frameId++,
    };

    NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, msg);

    if (input.type == InputTypeEnum.ActorMove) {
      DataManager.Instance.applyInput(input);

      this.pendingMsg.push(msg);
    }
  }

  handleServerSync(data: IMsgServerSync) {
    DataManager.Instance.state = DataManager.Instance.lastState;

    const { inputs, lastFrameId } = data;

    for (const input of inputs) {
      DataManager.Instance.applyInput(input);
    }

    DataManager.Instance.lastState = deepClone(DataManager.Instance.state);

    this.pendingMsg = this.pendingMsg.filter(
      (msg) => msg.frameId > lastFrameId
    );

    for (const msg of this.pendingMsg) {
      DataManager.Instance.applyInput(msg.input);
    }
  }

  async connetServer() {
    if (!(await NetworkManager.Instance.connect().catch(() => false))) {
      await new Promise((rs) => setTimeout(rs, 1000));
      await this.connetServer();
    }
  }

  initMap() {
    let mapPref = DataManager.Instance.mapPrefab.get(EntityTypeEnum.Map);

    let map = instantiate(mapPref);
    map.setParent(this.stage);
  }

  update(deltaTime: number) {
    if (!this.finishLoad) {
      return;
    }

    this.render();
    this.tick(deltaTime);
  }

  render(): void {
    this.renderActor();
    this.renderBullet();
  }

  tick(deltaTime: number) {
    this.tickActor(deltaTime);
    this.timePast(deltaTime);
  }

  timePast(deltaTime: number) {
    // DataManager.Instance.applyInput({
    //   type: InputTypeEnum.TimePast,
    //   deltaTime: deltaTime,
    // });
  }

  tickActor(deltaTime: number) {
    for (const data of DataManager.Instance.state.actors) {
      let am = DataManager.Instance.mapActor.get(data.id);

      am.tick(deltaTime);
    }
  }

  renderBullet() {
    for (const data of DataManager.Instance.state.bullets) {
      let thisBullet = DataManager.Instance.mapBullet.get(data.id);

      if (!thisBullet) {
        let newBullet = ObjectPoolManager.Instance.get(data.type);
        newBullet.active = false;

        let bm =
          newBullet.getComponent(BulletManager) ||
          newBullet.addComponent(BulletManager);

        DataManager.Instance.mapBullet.set(data.id, bm);

        bm.init(data);
      } else {
        thisBullet.render(data);
      }
    }
  }

  renderActor() {
    for (const actorState of DataManager.Instance.state.actors) {
      let thisActor = DataManager.Instance.mapActor.get(actorState.id);

      if (!thisActor) {
        let pref = DataManager.Instance.mapPrefab.get(actorState.type);
        let newActor = instantiate(pref);
        newActor.setParent(this.stage);
        let am = newActor.addComponent(ActorManager);

        DataManager.Instance.mapActor.set(actorState.id, am);

        am.init(actorState);
      } else {
        thisActor.render(actorState);
      }
    }
  }
}
